package service

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"inspector/internal/config"
	store "inspector/internal/dao"
	"inspector/internal/model/do"
	"inspector/internal/model/entity"
	"inspector/internal/utils"
	"inspector/pkg/code"
	"inspector/pkg/errors"
	"inspector/pkg/log"
	"io"
	"net/http"
	"os"
	"time"
)

type OAuthSrv interface {
}

type OAuthService struct {
	store store.Factory
	key   []byte
}

var _ OAuthSrv = (*OAuthService)(nil)

func NewOAuthService(store store.Factory) *OAuthService {
	key := os.Getenv("ENCRYPTION_KEY")
	if key == "" {
		key = "e137f61e078b6570f4639089b3b24bf9"
	}
	return &OAuthService{
		store: store,
		key:   []byte(key),
	}
}

func (s OAuthService) ProcessCallback(ctx context.Context, request do.CallbackParams) error {
	log.Infof("OAuth callback params: %v", request)
	res, err := exchangeCodeForToken(request.Code)
	if err != nil {
		log.Errorf("OAuth token exchange error: %v", err)
		return err
	}
	log.Infof("OAuth token response: %v", res)
	id, err := getUserInfo(res.AccessToken)
	if err != nil {
		log.Errorf("OAuth get user info error: %v", err)
		return err
	}
	log.Infof("OAuth user id: %v", id)
	tx := s.store.DB()
	accessToken, err := utils.EncryptAndEncodeBase64(res.AccessToken, s.key)
	if err != nil {
		return err
	}
	refreshToken, err := utils.EncryptAndEncodeBase64(res.RefreshToken, s.key)
	if err != nil {
		return err
	}
	err = s.store.User(tx).Create(ctx, &entity.User{
		AtomGitId:             id,
		InstallationId:        request.InstallationID,
		AccessToken:           accessToken,
		RefreshToken:          refreshToken,
		ExpiresIn:             res.ExpiresIn,
		RefreshTokenExpiresIn: res.RefreshTokenExpiresIn,
	})

	return nil
}

func getUserInfo(accessToken string) (string, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", fmt.Sprintf("%s/user/info", config.Get().GetAtomConfig().BaseApiUrl), nil)
	if err != nil {
		return "", err
	}

	req.Header.Add("Authorization", "Bearer "+accessToken)

	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	var result map[string]interface{}
	if err := json.Unmarshal(body, &result); err != nil {
		return "", err
	}

	id, ok := result["id"].(string)
	if !ok {
		return "", errors.New(code.NewErrBadRequest(), "Could not matching AtomGit user info.")
	}

	return id, nil
}

func exchangeCodeForToken(code string) (*do.TokenResponse, error) {
	reqBody, err := json.Marshal(do.TokenExchangeRequest{
		Code:         code,
		ClientID:     config.Get().GetAtomConfig().ClientId,
		ClientSecret: config.Get().GetAtomConfig().ClientSecrets,
	})
	if err != nil {
		return nil, err
	}
	log.Infof("OAuth token exchange request body: %v", string(reqBody))

	resp, err := http.Post(fmt.Sprintf("%s/login/oauth/access_token", config.Get().GetAtomConfig().BaseApiUrl), "application/json", bytes.NewBuffer(reqBody))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	log.Infof("OAuth token exchange response body: %v", string(body))

	var tokenResponse do.TokenResponse

	if err := json.Unmarshal(body, &tokenResponse); err != nil {
		return nil, err
	}

	return &tokenResponse, nil
}

func (s OAuthService) GetToken(ctx context.Context, request do.GetTokenRequest) (*do.TokenResponse, error) {
	tx := s.store.DB()
	user, err := s.store.User(tx).GetByInstallationId(ctx, request.ID)
	if err != nil {
		return nil, err
	}

	// Convert the expiration time from milliseconds to time.Time
	expiresAt := time.Unix(0, user.ExpiresIn*int64(time.Millisecond))

	// Check if the token is about to expire in less than 2 hours
	if time.Until(expiresAt) < 2*time.Hour {
		// Refresh the token
		refreshToken, err := utils.DecodeBase64AndDecrypt(user.RefreshToken, s.key)
		if err != nil {
			return nil, err
		}
		res, err := s.refreshToken(refreshToken)
		if err != nil {
			return nil, err
		}

		// Update the user's access token and expiration time
		accessToken, err := utils.EncryptAndEncodeBase64(res.AccessToken, s.key)
		if err != nil {
			return nil, err
		}
		refreshToken, err = utils.EncryptAndEncodeBase64(res.RefreshToken, s.key)
		if err != nil {
			return nil, err
		}
		user.AccessToken = accessToken
		user.ExpiresIn = res.ExpiresIn
		user.RefreshToken = refreshToken
		user.RefreshTokenExpiresIn = res.RefreshTokenExpiresIn

		// Save the updated user information
		err = s.store.User(tx).Update(ctx, user)
		if err != nil {
			return nil, err
		}
	}
	transferToken, err := utils.DecodeBase64AndDecrypt(user.AccessToken, s.key)

	return &do.TokenResponse{
		AccessToken: transferToken,
		ExpiresIn:   user.ExpiresIn,
	}, nil
}

func (s OAuthService) refreshToken(refreshToken string) (*do.TokenResponse, error) {
	// Prepare the request body
	body := &do.RefreshTokenRequest{
		ClientID:     config.Get().GetAtomConfig().ClientId,
		ClientSecret: config.Get().GetAtomConfig().ClientSecrets,
		GrantType:    "refresh_token",
		RefreshToken: refreshToken,
	}
	jsonData, err := json.Marshal(body)
	if err != nil {
		return nil, err
	}

	// Send the HTTP request
	req, err := http.NewRequest("POST", fmt.Sprintf("%s/login/oauth/access_token", config.Get().GetAtomConfig().BaseApiUrl), bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// Parse the response body
	respBody, _ := io.ReadAll(resp.Body)
	var tokenResponse do.TokenResponse
	err = json.Unmarshal(respBody, &tokenResponse)
	if err != nil {
		return nil, err
	}

	return &tokenResponse, nil
}
